package FIT::Runner::Tally;

use FIT::Class;
use Term::ANSIColor;
use FIT::Runner::Tally::Types;
use aliased 'FIT::Util::ClassLoader';
use aliased 'FIT::Fixture::Runner' => 'FixtureRunner';

use Config::System Use_Color => {isa => 'Bool'};

# classes we advise
use FIT::Document::Transformer;

my $Prefix = 'FIT::Document::Transformer::';

# only if we are not silent, add transforms to tally
our $Should_Count = 1; 

# class and advice setup -------------------------------------------------------

# for each possible test result create the attribute which stores its hit count
# load the transformer class, and augment required methods
for my $result_slot (tally_types) {

    # attribute for possible result
    has $result_slot => (
        is       => 'ro',
        isa      => 'Int',
        required => 1,
        default  => 0,
    );

    # lower case alias DWIM
    __PACKAGE__->meta->add_method
        (lc($result_slot) => sub { shift->$result_slot(@_) });

    # sub to increment result count
    __PACKAGE__->meta->add_method
        ("inc_$result_slot" => sub { shift->{$result_slot}++ });

    # TODO: is this needed? transformer is loaded in Transformer.pm
    my $transformer_package = "FIT::Document::Transformer::$result_slot";
    ClassLoader->load($transformer_package);

    # inc the correct slot depending on the transform, after a transform
    $transformer_package->meta->
        add_after_method_modifier(_transform => \&add_transform);
}

# don't add to tally test results run from run_fixtures_silently
FixtureRunner->meta->add_around_method_modifier
    (run_fixtures_silently => \&run_silently);

# call with a code ref to run the code with no writing to tally
# all errors, wrongs, etc. will not be counted
# run_fixtures_silently uses this for example
# run_fixtures_silently is used when you want to run fixtures without
# counting their assertions, e.g. Fixture::Specify
sub run_silently() {
    # can be called as function or as method
    shift if ref($_[0]) ne 'CODE';
    my ($next, @args) = @_;
    local $Should_Count = 0;
    $next->(@args);
}

# after FIT::Document::Transform::*::_transform
sub add_transform() {
    my ($transform, $element) = @_;
    my $context = $element->fit_context;
    return unless $context; # not in the callflow of FIT::Runner::run has no context
    return unless $Should_Count;

    do { # for each runner context in stack
        (my $slot = ref $transform) =~ s/$Prefix//o;
        my $method = "inc_$slot";
        $context->tally->$method;
    } while $context = $context->parent;
}

# public interface -------------------------------------------------------------

sub is_ok { $self->Wrong || $self->Error? 0: 1 }

sub unix_exit_code { $self->is_ok? 0: 1 }

sub as_summary_line {
    local $_;
    return join ', ', map { "$_: ". $self->$_ } lc_tally_types;
}

sub as_summary_text {
    local $_;
    my $color = $self->is_ok? 'green': 'red';
    my $summary_word =
        ($Use_Color ? (color $color): '').
        '* '. $self->as_summary_word. ' *'.
        ($Use_Color ? (color 'reset'): '');
    return
        $self->as_summary_line. "\n$summary_word\n\n";
}

sub as_summary_word { $self->is_ok? 'OK': 'FAIL' }


=head1 NAME

FIT::Runner::Tally - keep count of test results


=head1 SYNOPSIS

    use aliased 'FIT::Runner::Tally';

    my $tally = Tally->new;


=head1 DESCRIPTION

A tally object counts the number of hits for each possible test result. This
is used to present the tester with a test summary, showing how many tests
were right, how many were wrong, and so on, for each possible test result.

=cut

